fal 1.2.2__tar.gz → 1.2.3__tar.gz

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.

Potentially problematic release.


This version of fal might be problematic. Click here for more details.

Files changed (158) hide show
  1. {fal-1.2.2 → fal-1.2.3}/PKG-INFO +1 -1
  2. {fal-1.2.2 → fal-1.2.3}/fal.egg-info/PKG-INFO +1 -1
  3. {fal-1.2.2 → fal-1.2.3}/fal.egg-info/SOURCES.txt +6 -0
  4. {fal-1.2.2 → fal-1.2.3}/src/fal/_fal_version.py +2 -2
  5. {fal-1.2.2 → fal-1.2.3}/src/fal/app.py +1 -1
  6. fal-1.2.3/src/fal/toolkit/image/__init__.py +74 -0
  7. fal-1.2.3/src/fal/toolkit/image/nsfw_filter/__init__.py +11 -0
  8. fal-1.2.3/src/fal/toolkit/image/nsfw_filter/env.py +9 -0
  9. fal-1.2.3/src/fal/toolkit/image/nsfw_filter/inference.py +77 -0
  10. fal-1.2.3/src/fal/toolkit/image/nsfw_filter/model.py +18 -0
  11. fal-1.2.3/src/fal/toolkit/image/nsfw_filter/requirements.txt +4 -0
  12. fal-1.2.3/src/fal/toolkit/image/safety_checker.py +107 -0
  13. fal-1.2.2/src/fal/toolkit/image/__init__.py +0 -3
  14. {fal-1.2.2 → fal-1.2.3}/.gitignore +0 -0
  15. {fal-1.2.2 → fal-1.2.3}/README.md +0 -0
  16. {fal-1.2.2 → fal-1.2.3}/fal.egg-info/dependency_links.txt +0 -0
  17. {fal-1.2.2 → fal-1.2.3}/fal.egg-info/entry_points.txt +0 -0
  18. {fal-1.2.2 → fal-1.2.3}/fal.egg-info/requires.txt +0 -0
  19. {fal-1.2.2 → fal-1.2.3}/fal.egg-info/top_level.txt +0 -0
  20. {fal-1.2.2 → fal-1.2.3}/openapi-fal-rest/README.md +0 -0
  21. {fal-1.2.2 → fal-1.2.3}/openapi-fal-rest/openapi_fal_rest/__init__.py +0 -0
  22. {fal-1.2.2 → fal-1.2.3}/openapi-fal-rest/openapi_fal_rest/api/__init__.py +0 -0
  23. {fal-1.2.2 → fal-1.2.3}/openapi-fal-rest/openapi_fal_rest/api/applications/__init__.py +0 -0
  24. {fal-1.2.2 → fal-1.2.3}/openapi-fal-rest/openapi_fal_rest/api/applications/app_metadata.py +0 -0
  25. {fal-1.2.2 → fal-1.2.3}/openapi-fal-rest/openapi_fal_rest/api/billing/__init__.py +0 -0
  26. {fal-1.2.2 → fal-1.2.3}/openapi-fal-rest/openapi_fal_rest/api/billing/get_user_details.py +0 -0
  27. {fal-1.2.2 → fal-1.2.3}/openapi-fal-rest/openapi_fal_rest/api/comfy/__init__.py +0 -0
  28. {fal-1.2.2 → fal-1.2.3}/openapi-fal-rest/openapi_fal_rest/api/comfy/create_workflow.py +0 -0
  29. {fal-1.2.2 → fal-1.2.3}/openapi-fal-rest/openapi_fal_rest/api/comfy/delete_workflow.py +0 -0
  30. {fal-1.2.2 → fal-1.2.3}/openapi-fal-rest/openapi_fal_rest/api/comfy/get_workflow.py +0 -0
  31. {fal-1.2.2 → fal-1.2.3}/openapi-fal-rest/openapi_fal_rest/api/comfy/list_user_workflows.py +0 -0
  32. {fal-1.2.2 → fal-1.2.3}/openapi-fal-rest/openapi_fal_rest/api/comfy/update_workflow.py +0 -0
  33. {fal-1.2.2 → fal-1.2.3}/openapi-fal-rest/openapi_fal_rest/api/files/__init__.py +0 -0
  34. {fal-1.2.2 → fal-1.2.3}/openapi-fal-rest/openapi_fal_rest/api/files/check_dir_hash.py +0 -0
  35. {fal-1.2.2 → fal-1.2.3}/openapi-fal-rest/openapi_fal_rest/api/files/upload_local_file.py +0 -0
  36. {fal-1.2.2 → fal-1.2.3}/openapi-fal-rest/openapi_fal_rest/api/users/__init__.py +0 -0
  37. {fal-1.2.2 → fal-1.2.3}/openapi-fal-rest/openapi_fal_rest/api/users/get_current_user.py +0 -0
  38. {fal-1.2.2 → fal-1.2.3}/openapi-fal-rest/openapi_fal_rest/api/workflows/__init__.py +0 -0
  39. {fal-1.2.2 → fal-1.2.3}/openapi-fal-rest/openapi_fal_rest/api/workflows/create_workflow.py +0 -0
  40. {fal-1.2.2 → fal-1.2.3}/openapi-fal-rest/openapi_fal_rest/api/workflows/delete_workflow.py +0 -0
  41. {fal-1.2.2 → fal-1.2.3}/openapi-fal-rest/openapi_fal_rest/api/workflows/get_workflow.py +0 -0
  42. {fal-1.2.2 → fal-1.2.3}/openapi-fal-rest/openapi_fal_rest/api/workflows/list_user_workflows.py +0 -0
  43. {fal-1.2.2 → fal-1.2.3}/openapi-fal-rest/openapi_fal_rest/api/workflows/update_workflow.py +0 -0
  44. {fal-1.2.2 → fal-1.2.3}/openapi-fal-rest/openapi_fal_rest/client.py +0 -0
  45. {fal-1.2.2 → fal-1.2.3}/openapi-fal-rest/openapi_fal_rest/errors.py +0 -0
  46. {fal-1.2.2 → fal-1.2.3}/openapi-fal-rest/openapi_fal_rest/models/__init__.py +0 -0
  47. {fal-1.2.2 → fal-1.2.3}/openapi-fal-rest/openapi_fal_rest/models/app_metadata_response_app_metadata.py +0 -0
  48. {fal-1.2.2 → fal-1.2.3}/openapi-fal-rest/openapi_fal_rest/models/body_upload_local_file.py +0 -0
  49. {fal-1.2.2 → fal-1.2.3}/openapi-fal-rest/openapi_fal_rest/models/comfy_workflow_detail.py +0 -0
  50. {fal-1.2.2 → fal-1.2.3}/openapi-fal-rest/openapi_fal_rest/models/comfy_workflow_item.py +0 -0
  51. {fal-1.2.2 → fal-1.2.3}/openapi-fal-rest/openapi_fal_rest/models/comfy_workflow_schema.py +0 -0
  52. {fal-1.2.2 → fal-1.2.3}/openapi-fal-rest/openapi_fal_rest/models/comfy_workflow_schema_extra_data.py +0 -0
  53. {fal-1.2.2 → fal-1.2.3}/openapi-fal-rest/openapi_fal_rest/models/comfy_workflow_schema_fal_inputs.py +0 -0
  54. {fal-1.2.2 → fal-1.2.3}/openapi-fal-rest/openapi_fal_rest/models/comfy_workflow_schema_fal_inputs_dev_info.py +0 -0
  55. {fal-1.2.2 → fal-1.2.3}/openapi-fal-rest/openapi_fal_rest/models/comfy_workflow_schema_prompt.py +0 -0
  56. {fal-1.2.2 → fal-1.2.3}/openapi-fal-rest/openapi_fal_rest/models/current_user.py +0 -0
  57. {fal-1.2.2 → fal-1.2.3}/openapi-fal-rest/openapi_fal_rest/models/customer_details.py +0 -0
  58. {fal-1.2.2 → fal-1.2.3}/openapi-fal-rest/openapi_fal_rest/models/hash_check.py +0 -0
  59. {fal-1.2.2 → fal-1.2.3}/openapi-fal-rest/openapi_fal_rest/models/http_validation_error.py +0 -0
  60. {fal-1.2.2 → fal-1.2.3}/openapi-fal-rest/openapi_fal_rest/models/lock_reason.py +0 -0
  61. {fal-1.2.2 → fal-1.2.3}/openapi-fal-rest/openapi_fal_rest/models/page_comfy_workflow_item.py +0 -0
  62. {fal-1.2.2 → fal-1.2.3}/openapi-fal-rest/openapi_fal_rest/models/page_workflow_item.py +0 -0
  63. {fal-1.2.2 → fal-1.2.3}/openapi-fal-rest/openapi_fal_rest/models/team_role.py +0 -0
  64. {fal-1.2.2 → fal-1.2.3}/openapi-fal-rest/openapi_fal_rest/models/typed_comfy_workflow.py +0 -0
  65. {fal-1.2.2 → fal-1.2.3}/openapi-fal-rest/openapi_fal_rest/models/typed_comfy_workflow_update.py +0 -0
  66. {fal-1.2.2 → fal-1.2.3}/openapi-fal-rest/openapi_fal_rest/models/typed_workflow.py +0 -0
  67. {fal-1.2.2 → fal-1.2.3}/openapi-fal-rest/openapi_fal_rest/models/typed_workflow_update.py +0 -0
  68. {fal-1.2.2 → fal-1.2.3}/openapi-fal-rest/openapi_fal_rest/models/user_member.py +0 -0
  69. {fal-1.2.2 → fal-1.2.3}/openapi-fal-rest/openapi_fal_rest/models/validation_error.py +0 -0
  70. {fal-1.2.2 → fal-1.2.3}/openapi-fal-rest/openapi_fal_rest/models/workflow_contents.py +0 -0
  71. {fal-1.2.2 → fal-1.2.3}/openapi-fal-rest/openapi_fal_rest/models/workflow_contents_metadata.py +0 -0
  72. {fal-1.2.2 → fal-1.2.3}/openapi-fal-rest/openapi_fal_rest/models/workflow_contents_nodes.py +0 -0
  73. {fal-1.2.2 → fal-1.2.3}/openapi-fal-rest/openapi_fal_rest/models/workflow_contents_output.py +0 -0
  74. {fal-1.2.2 → fal-1.2.3}/openapi-fal-rest/openapi_fal_rest/models/workflow_detail.py +0 -0
  75. {fal-1.2.2 → fal-1.2.3}/openapi-fal-rest/openapi_fal_rest/models/workflow_detail_contents.py +0 -0
  76. {fal-1.2.2 → fal-1.2.3}/openapi-fal-rest/openapi_fal_rest/models/workflow_item.py +0 -0
  77. {fal-1.2.2 → fal-1.2.3}/openapi-fal-rest/openapi_fal_rest/models/workflow_node.py +0 -0
  78. {fal-1.2.2 → fal-1.2.3}/openapi-fal-rest/openapi_fal_rest/models/workflow_node_type.py +0 -0
  79. {fal-1.2.2 → fal-1.2.3}/openapi-fal-rest/openapi_fal_rest/models/workflow_schema.py +0 -0
  80. {fal-1.2.2 → fal-1.2.3}/openapi-fal-rest/openapi_fal_rest/models/workflow_schema_input.py +0 -0
  81. {fal-1.2.2 → fal-1.2.3}/openapi-fal-rest/openapi_fal_rest/models/workflow_schema_output.py +0 -0
  82. {fal-1.2.2 → fal-1.2.3}/openapi-fal-rest/openapi_fal_rest/py.typed +0 -0
  83. {fal-1.2.2 → fal-1.2.3}/openapi-fal-rest/openapi_fal_rest/types.py +0 -0
  84. {fal-1.2.2 → fal-1.2.3}/openapi-fal-rest/pyproject.toml +0 -0
  85. {fal-1.2.2 → fal-1.2.3}/openapi_rest.config.yaml +0 -0
  86. {fal-1.2.2 → fal-1.2.3}/pyproject.toml +0 -0
  87. {fal-1.2.2 → fal-1.2.3}/setup.cfg +0 -0
  88. {fal-1.2.2 → fal-1.2.3}/src/fal/__init__.py +0 -0
  89. {fal-1.2.2 → fal-1.2.3}/src/fal/__main__.py +0 -0
  90. {fal-1.2.2 → fal-1.2.3}/src/fal/_serialization.py +0 -0
  91. {fal-1.2.2 → fal-1.2.3}/src/fal/_version.py +0 -0
  92. {fal-1.2.2 → fal-1.2.3}/src/fal/api.py +0 -0
  93. {fal-1.2.2 → fal-1.2.3}/src/fal/apps.py +0 -0
  94. {fal-1.2.2 → fal-1.2.3}/src/fal/auth/__init__.py +0 -0
  95. {fal-1.2.2 → fal-1.2.3}/src/fal/auth/auth0.py +0 -0
  96. {fal-1.2.2 → fal-1.2.3}/src/fal/auth/local.py +0 -0
  97. {fal-1.2.2 → fal-1.2.3}/src/fal/cli/__init__.py +0 -0
  98. {fal-1.2.2 → fal-1.2.3}/src/fal/cli/apps.py +0 -0
  99. {fal-1.2.2 → fal-1.2.3}/src/fal/cli/auth.py +0 -0
  100. {fal-1.2.2 → fal-1.2.3}/src/fal/cli/create.py +0 -0
  101. {fal-1.2.2 → fal-1.2.3}/src/fal/cli/debug.py +0 -0
  102. {fal-1.2.2 → fal-1.2.3}/src/fal/cli/deploy.py +0 -0
  103. {fal-1.2.2 → fal-1.2.3}/src/fal/cli/doctor.py +0 -0
  104. {fal-1.2.2 → fal-1.2.3}/src/fal/cli/keys.py +0 -0
  105. {fal-1.2.2 → fal-1.2.3}/src/fal/cli/main.py +0 -0
  106. {fal-1.2.2 → fal-1.2.3}/src/fal/cli/parser.py +0 -0
  107. {fal-1.2.2 → fal-1.2.3}/src/fal/cli/run.py +0 -0
  108. {fal-1.2.2 → fal-1.2.3}/src/fal/cli/secrets.py +0 -0
  109. {fal-1.2.2 → fal-1.2.3}/src/fal/console/__init__.py +0 -0
  110. {fal-1.2.2 → fal-1.2.3}/src/fal/console/icons.py +0 -0
  111. {fal-1.2.2 → fal-1.2.3}/src/fal/console/ux.py +0 -0
  112. {fal-1.2.2 → fal-1.2.3}/src/fal/container.py +0 -0
  113. {fal-1.2.2 → fal-1.2.3}/src/fal/exceptions/__init__.py +0 -0
  114. {fal-1.2.2 → fal-1.2.3}/src/fal/exceptions/_base.py +0 -0
  115. {fal-1.2.2 → fal-1.2.3}/src/fal/exceptions/auth.py +0 -0
  116. {fal-1.2.2 → fal-1.2.3}/src/fal/flags.py +0 -0
  117. {fal-1.2.2 → fal-1.2.3}/src/fal/logging/__init__.py +0 -0
  118. {fal-1.2.2 → fal-1.2.3}/src/fal/logging/isolate.py +0 -0
  119. {fal-1.2.2 → fal-1.2.3}/src/fal/logging/style.py +0 -0
  120. {fal-1.2.2 → fal-1.2.3}/src/fal/logging/trace.py +0 -0
  121. {fal-1.2.2 → fal-1.2.3}/src/fal/logging/user.py +0 -0
  122. {fal-1.2.2 → fal-1.2.3}/src/fal/py.typed +0 -0
  123. {fal-1.2.2 → fal-1.2.3}/src/fal/rest_client.py +0 -0
  124. {fal-1.2.2 → fal-1.2.3}/src/fal/sdk.py +0 -0
  125. {fal-1.2.2 → fal-1.2.3}/src/fal/sync.py +0 -0
  126. {fal-1.2.2 → fal-1.2.3}/src/fal/toolkit/__init__.py +0 -0
  127. {fal-1.2.2 → fal-1.2.3}/src/fal/toolkit/exceptions.py +0 -0
  128. {fal-1.2.2 → fal-1.2.3}/src/fal/toolkit/file/__init__.py +0 -0
  129. {fal-1.2.2 → fal-1.2.3}/src/fal/toolkit/file/file.py +0 -0
  130. {fal-1.2.2 → fal-1.2.3}/src/fal/toolkit/file/providers/fal.py +0 -0
  131. {fal-1.2.2 → fal-1.2.3}/src/fal/toolkit/file/providers/gcp.py +0 -0
  132. {fal-1.2.2 → fal-1.2.3}/src/fal/toolkit/file/providers/r2.py +0 -0
  133. {fal-1.2.2 → fal-1.2.3}/src/fal/toolkit/file/types.py +0 -0
  134. {fal-1.2.2 → fal-1.2.3}/src/fal/toolkit/image/image.py +0 -0
  135. {fal-1.2.2 → fal-1.2.3}/src/fal/toolkit/optimize.py +0 -0
  136. {fal-1.2.2 → fal-1.2.3}/src/fal/toolkit/utils/__init__.py +0 -0
  137. {fal-1.2.2 → fal-1.2.3}/src/fal/toolkit/utils/download_utils.py +0 -0
  138. {fal-1.2.2 → fal-1.2.3}/src/fal/utils.py +0 -0
  139. {fal-1.2.2 → fal-1.2.3}/src/fal/workflows.py +0 -0
  140. {fal-1.2.2 → fal-1.2.3}/tests/__init__.py +0 -0
  141. {fal-1.2.2 → fal-1.2.3}/tests/cli/__init__.py +0 -0
  142. {fal-1.2.2 → fal-1.2.3}/tests/cli/test_apps.py +0 -0
  143. {fal-1.2.2 → fal-1.2.3}/tests/cli/test_auth.py +0 -0
  144. {fal-1.2.2 → fal-1.2.3}/tests/cli/test_deploy.py +0 -0
  145. {fal-1.2.2 → fal-1.2.3}/tests/cli/test_keys.py +0 -0
  146. {fal-1.2.2 → fal-1.2.3}/tests/cli/test_run.py +0 -0
  147. {fal-1.2.2 → fal-1.2.3}/tests/cli/test_secrets.py +0 -0
  148. {fal-1.2.2 → fal-1.2.3}/tests/conftest.py +0 -0
  149. {fal-1.2.2 → fal-1.2.3}/tests/integration_test.py +0 -0
  150. {fal-1.2.2 → fal-1.2.3}/tests/mainify_package/__init__.py +0 -0
  151. {fal-1.2.2 → fal-1.2.3}/tests/mainify_package/impl.py +0 -0
  152. {fal-1.2.2 → fal-1.2.3}/tests/mainify_package/utils.py +0 -0
  153. {fal-1.2.2 → fal-1.2.3}/tests/mainify_target.py +0 -0
  154. {fal-1.2.2 → fal-1.2.3}/tests/test_apps.py +0 -0
  155. {fal-1.2.2 → fal-1.2.3}/tests/test_stability.py +0 -0
  156. {fal-1.2.2 → fal-1.2.3}/tests/toolkit/file_test.py +0 -0
  157. {fal-1.2.2 → fal-1.2.3}/tests/toolkit/image_test.py +0 -0
  158. {fal-1.2.2 → fal-1.2.3}/tools/demo_script.py +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.1
2
2
  Name: fal
3
- Version: 1.2.2
3
+ Version: 1.2.3
4
4
  Summary: fal is an easy-to-use Serverless Python Framework
5
5
  Author: Features & Labels <support@fal.ai>
6
6
  Requires-Python: >=3.8
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.1
2
2
  Name: fal
3
- Version: 1.2.2
3
+ Version: 1.2.3
4
4
  Summary: fal is an easy-to-use Serverless Python Framework
5
5
  Author: Features & Labels <support@fal.ai>
6
6
  Requires-Python: >=3.8
@@ -126,6 +126,12 @@ src/fal/toolkit/file/providers/gcp.py
126
126
  src/fal/toolkit/file/providers/r2.py
127
127
  src/fal/toolkit/image/__init__.py
128
128
  src/fal/toolkit/image/image.py
129
+ src/fal/toolkit/image/safety_checker.py
130
+ src/fal/toolkit/image/nsfw_filter/__init__.py
131
+ src/fal/toolkit/image/nsfw_filter/env.py
132
+ src/fal/toolkit/image/nsfw_filter/inference.py
133
+ src/fal/toolkit/image/nsfw_filter/model.py
134
+ src/fal/toolkit/image/nsfw_filter/requirements.txt
129
135
  src/fal/toolkit/utils/__init__.py
130
136
  src/fal/toolkit/utils/download_utils.py
131
137
  tests/__init__.py
@@ -12,5 +12,5 @@ __version__: str
12
12
  __version_tuple__: VERSION_TUPLE
13
13
  version_tuple: VERSION_TUPLE
14
14
 
15
- __version__ = version = '1.2.2'
16
- __version_tuple__ = version_tuple = (1, 2, 2)
15
+ __version__ = version = '1.2.3'
16
+ __version_tuple__ = version_tuple = (1, 2, 3)
@@ -158,7 +158,7 @@ class App(fal.api.BaseServable):
158
158
  app_name = kwargs.pop("name", None) or _to_fal_app_name(cls.__name__)
159
159
  parent_settings = getattr(cls, "host_kwargs", {})
160
160
  cls.host_kwargs = {**parent_settings, **kwargs}
161
- cls.app_name = app_name
161
+ cls.app_name = getattr(cls, "app_name", app_name)
162
162
 
163
163
  if cls.__init__ is not App.__init__:
164
164
  raise ValueError(
@@ -0,0 +1,74 @@
1
+ from __future__ import annotations
2
+
3
+ from functools import lru_cache
4
+ from typing import TYPE_CHECKING
5
+ from urllib.request import Request, urlopen
6
+
7
+ from .image import * # noqa: F403
8
+
9
+ if TYPE_CHECKING:
10
+ from PIL.Image import Image as PILImage
11
+
12
+
13
+ def filter_by(
14
+ has_nsfw_concepts: list[bool],
15
+ images: list[PILImage],
16
+ ) -> list[PILImage]:
17
+ from PIL import Image as PILImage
18
+
19
+ return [
20
+ (
21
+ PILImage.new("RGB", (image.width, image.height), (0, 0, 0))
22
+ if has_nsfw
23
+ else image
24
+ )
25
+ for image, has_nsfw in zip(images, has_nsfw_concepts)
26
+ ]
27
+
28
+
29
+ def preprocess_image(image_pil, convert_to_rgb=True, fix_orientation=True):
30
+ from PIL import ImageOps, ImageSequence
31
+
32
+ # For MPO (multi picture object) format images, we only need the first image
33
+ images = []
34
+ for image in ImageSequence.Iterator(image_pil):
35
+ img = image
36
+
37
+ if convert_to_rgb:
38
+ img = img.convert("RGB")
39
+
40
+ if fix_orientation:
41
+ img = ImageOps.exif_transpose(img)
42
+
43
+ images.append(img)
44
+
45
+ break
46
+
47
+ return images[0]
48
+
49
+
50
+ @lru_cache(maxsize=64)
51
+ def read_image_from_url(
52
+ url: str, convert_to_rgb: bool = True, fix_orientation: bool = True
53
+ ):
54
+ from fastapi import HTTPException
55
+ from PIL import Image
56
+
57
+ TEMP_HEADERS = {
58
+ "User-Agent": (
59
+ "Mozilla/5.0 (Macintosh; Intel Mac OS X 10.8; rv:21.0) "
60
+ "Gecko/20100101 Firefox/21.0"
61
+ ),
62
+ }
63
+
64
+ try:
65
+ request = Request(url, headers=TEMP_HEADERS)
66
+ response = urlopen(request)
67
+ image_pil = Image.open(response)
68
+ except Exception:
69
+ import traceback
70
+
71
+ traceback.print_exc()
72
+ raise HTTPException(422, f"Could not load image from url: {url}")
73
+
74
+ return preprocess_image(image_pil, convert_to_rgb, fix_orientation)
@@ -0,0 +1,11 @@
1
+ from .inference import (
2
+ NSFWImageDetectionInput,
3
+ NSFWImageDetectionOutput,
4
+ run_nsfw_estimation,
5
+ )
6
+
7
+ __all__ = [
8
+ "NSFWImageDetectionInput",
9
+ "NSFWImageDetectionOutput",
10
+ "run_nsfw_estimation",
11
+ ]
@@ -0,0 +1,9 @@
1
+ from pathlib import Path
2
+
3
+ CURR_DIR = Path(__file__).resolve().parent
4
+
5
+
6
+ def get_requirements():
7
+ with open(CURR_DIR / "requirements.txt") as fp:
8
+ requirements = fp.read().split()
9
+ return requirements
@@ -0,0 +1,77 @@
1
+ from pydantic import BaseModel, Field
2
+
3
+ import fal
4
+ from fal.toolkit.image import read_image_from_url
5
+
6
+ from .env import get_requirements
7
+ from .model import get_model
8
+
9
+
10
+ class NSFWImageDetectionInput(BaseModel):
11
+ image_url: str = Field(
12
+ description="Input image url.",
13
+ examples=[
14
+ "https://storage.googleapis.com/falserverless/model_tests/remove_background/elephant.jpg",
15
+ ],
16
+ )
17
+
18
+
19
+ class NSFWImageDetectionOutput(BaseModel):
20
+ nsfw_probability: float = Field(
21
+ description="The probability of the image being NSFW.",
22
+ )
23
+
24
+
25
+ def check_nsfw_content(pil_image: object):
26
+ import torch
27
+
28
+ model, processor = get_model()
29
+
30
+ with torch.no_grad():
31
+ inputs = processor(images=pil_image, return_tensors="pt")
32
+ outputs = model(**inputs)
33
+ logits = outputs.logits.squeeze() # Remove batch dimension to simplify indexing
34
+
35
+ # Apply softmax to convert logits to probabilities
36
+ probabilities = torch.softmax(logits, dim=0)
37
+
38
+ nsfw_class_index = model.config.label2id.get(
39
+ "nsfw", None
40
+ ) # Replace "NSFW" with the exact class name if different
41
+
42
+ # Validate that NSFW class index is found
43
+ if nsfw_class_index is not None:
44
+ nsfw_probability = probabilities[int(nsfw_class_index)].item()
45
+ return nsfw_probability
46
+ else:
47
+ raise ValueError("NSFW class not found in model output.")
48
+
49
+
50
+ def run_nsfw_estimation(
51
+ input: NSFWImageDetectionInput,
52
+ ) -> NSFWImageDetectionOutput:
53
+ img = read_image_from_url(input.image_url)
54
+ nsfw_probability = check_nsfw_content(img)
55
+
56
+ return NSFWImageDetectionOutput(nsfw_probability=nsfw_probability)
57
+
58
+
59
+ @fal.function(
60
+ requirements=get_requirements(),
61
+ machine_type="GPU-A6000",
62
+ serve=True,
63
+ )
64
+ def run_nsfw_estimation_on_fal(
65
+ input: NSFWImageDetectionInput,
66
+ ) -> NSFWImageDetectionOutput:
67
+ return run_nsfw_estimation(input)
68
+
69
+
70
+ if __name__ == "__main__":
71
+ local = run_nsfw_estimation_on_fal.on(serve=False)
72
+ result = local(
73
+ NSFWImageDetectionInput(
74
+ image_url="https://storage.googleapis.com/falserverless/model_tests/remove_background/elephant.jpg",
75
+ )
76
+ )
77
+ print(result)
@@ -0,0 +1,18 @@
1
+ import fal
2
+
3
+
4
+ @fal.cached
5
+ def get_model():
6
+ import os
7
+
8
+ from transformers import AutoModelForImageClassification, ViTImageProcessor
9
+
10
+ os.environ["TRANSFORMERS_CACHE"] = "/data/models"
11
+ os.environ["HF_HOME"] = "/data/models"
12
+
13
+ model = AutoModelForImageClassification.from_pretrained(
14
+ "Falconsai/nsfw_image_detection"
15
+ )
16
+ processor = ViTImageProcessor.from_pretrained("Falconsai/nsfw_image_detection")
17
+
18
+ return model, processor
@@ -0,0 +1,4 @@
1
+ accelerate
2
+ Pillow
3
+ torch
4
+ transformers
@@ -0,0 +1,107 @@
1
+ from typing import Any
2
+
3
+ import fal
4
+
5
+ from . import filter_by
6
+ from .nsfw_filter.model import get_model
7
+
8
+
9
+ @fal.cached
10
+ def load_safety_checker():
11
+ import torch
12
+ from diffusers.pipelines.stable_diffusion.safety_checker import (
13
+ StableDiffusionSafetyChecker,
14
+ )
15
+ from transformers import AutoFeatureExtractor
16
+
17
+ feature_extractor = AutoFeatureExtractor.from_pretrained(
18
+ "CompVis/stable-diffusion-safety-checker",
19
+ torch_dtype="float16",
20
+ )
21
+ safety_checker = StableDiffusionSafetyChecker.from_pretrained(
22
+ "CompVis/stable-diffusion-safety-checker",
23
+ torch_dtype=torch.float16,
24
+ ).to("cuda")
25
+
26
+ return feature_extractor, safety_checker
27
+
28
+
29
+ def run_safety_checker(
30
+ pil_images: list[object],
31
+ ) -> list[bool]:
32
+ import numpy as np
33
+ import torch
34
+
35
+ feature_extractor, safety_checker = load_safety_checker()
36
+
37
+ safety_checker_input = feature_extractor(pil_images, return_tensors="pt").to("cuda")
38
+
39
+ np_image = [np.array(val) for val in pil_images]
40
+
41
+ _, has_nsfw_concept = safety_checker(
42
+ images=np_image,
43
+ clip_input=safety_checker_input.pixel_values.to(torch.float16),
44
+ )
45
+
46
+ return has_nsfw_concept
47
+
48
+
49
+ def run_safety_checker_v2(pil_images: list, nsfw_threshold: float = 0.5) -> list[bool]:
50
+ import torch
51
+
52
+ model, processor = get_model()
53
+
54
+ has_nsfw_concept = []
55
+
56
+ with torch.no_grad():
57
+ for pil_image in pil_images:
58
+ inputs = processor(
59
+ images=pil_image.convert("RGB"),
60
+ return_tensors="pt",
61
+ )
62
+ outputs = model(**inputs)
63
+ logits = (
64
+ outputs.logits.squeeze()
65
+ ) # Remove batch dimension to simplify indexing
66
+
67
+ # Apply softmax to convert logits to probabilities
68
+ probabilities = torch.softmax(logits, dim=0)
69
+
70
+ nsfw_class_index = model.config.label2id.get(
71
+ "nsfw", None
72
+ ) # Replace "NSFW" with the exact class name if different
73
+
74
+ # Validate that NSFW class index is found
75
+ if nsfw_class_index is not None:
76
+ nsfw_probability = probabilities[int(nsfw_class_index)].item()
77
+ print("NSFW probability:", nsfw_probability)
78
+ has_nsfw_concept.append(nsfw_probability > nsfw_threshold)
79
+ else:
80
+ raise ValueError("NSFW class not found in model output.")
81
+
82
+ return has_nsfw_concept
83
+
84
+
85
+ def postprocess_images(
86
+ pil_images: list[object],
87
+ enable_safety_checker: bool = True,
88
+ safety_checker_version: int = 2,
89
+ ) -> dict[str, Any]:
90
+ outputs: dict[str, list[Any]] = {
91
+ "images": pil_images,
92
+ }
93
+
94
+ if enable_safety_checker:
95
+ safety_checker_fn = (
96
+ run_safety_checker_v2 if safety_checker_version == 2 else run_safety_checker
97
+ )
98
+ outputs["has_nsfw_concepts"] = safety_checker_fn(pil_images) # type: ignore
99
+ else:
100
+ outputs["has_nsfw_concepts"] = [False] * len(pil_images)
101
+
102
+ outputs["images"] = filter_by(
103
+ outputs["has_nsfw_concepts"],
104
+ outputs["images"],
105
+ )
106
+
107
+ return outputs
@@ -1,3 +0,0 @@
1
- from __future__ import annotations
2
-
3
- from .image import * # noqa: F403
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes